package ch.subgate.pongwars;

import android.bluetooth.BluetoothSocket;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * StreamThread is a separate thread from the basic game loop and the android UI thread. Basically
 * this is where all the bluetooth communication for multi-player matches happens. There are
 * basically two modes StreamThread can operate in. The first is the server mode: When in server mode
 * An iteration follows the following steps:
 * 1. send local player and ball positions
 * 2. receive the other player's x position and handle it with setDICX()
 *
 * It is important to note that the device operating in client mode does not execute any update()
 * calls. All simulation is done on the server. The client therefore follows the following steps:
 * 1. send local players x position
 * 2. receive the game state
 */
public class StreamThread extends Thread {

    /** The bluetooth socket used for receiving / sending data */
    private BluetoothSocket mSocket;

    /** The outgoing data stream */
    private ObjectOutputStream mOut;

    /** The incoming data stream */
    private ObjectInputStream mIn;

    private boolean mIsRunning;

    private GameContextP2P mContext;

    private HumanPlayer mHumanPlayer;

    private NetworkPlayer mNetworkPlayer;

    private boolean mIsServer;

    public StreamThread(BluetoothSocket socket, GameContextP2P gameContext, boolean isServer) {
        mSocket = socket;
        mContext = gameContext;
        mIsRunning = true;
        mIsServer = isServer;
        if (gameContext.playerRed instanceof NetworkPlayer) {
            mHumanPlayer = (HumanPlayer)gameContext.playerBlue;
            mNetworkPlayer = (NetworkPlayer)gameContext.playerRed;
        } else {
            mHumanPlayer = (HumanPlayer)gameContext.playerRed;
            mNetworkPlayer = (NetworkPlayer)gameContext.playerBlue;
        } // if
        try {
            mOut = new ObjectOutputStream(mSocket.getOutputStream());
            mOut.flush();
            mIn = new ObjectInputStream(mSocket.getInputStream());
        } catch (Exception e) { /* do nothing */ }
    } // Constructor

    public void setIsRunning(boolean isRunning) { mIsRunning = isRunning; }

    @Override
    public void run() {
        while (mIsRunning) {
            try {
                receiveMessage();
            } catch (Exception e) { /* do nothing */ }
        } // while

        try {
            mIn.close();
            mOut.close();
            mSocket.close();
        } catch (IOException e) { /* do nothing */ }
    } // run

    public synchronized void sendMessage(int type) throws IOException {
        if (type > 0) {
            // communicate message content
            mOut.writeInt(type);

            // communicate match terms
            if ((type & Constants.FLAG_INIT) > 0) { // init always has highest priority and gets sent first
                mOut.writeFloat(mHumanPlayer.mSize);
            } // if

            // communicate game state
            if ((type & Constants.FLAG_STATE) > 0) {
                mOut.writeFloat(mHumanPlayer.mX);
                mOut.writeFloat(mContext.ball.mX);
                mOut.writeFloat(mContext.ball.mY);
                mOut.writeFloat(mContext.ball.bounceTime);
                mOut.writeFloat(mContext.ball.direction);
                mOut.writeFloat(mContext.ball.speed);
                mOut.writeFloat(mContext.ball.spin);
            } // if
        } // if
        mOut.flush();
    } // sendMessage

    public void receiveMessage() throws IOException {
        int type = mIn.readInt();

        /* handle flags without attached data */
        if ((type & Constants.FLAG_READY) > 0) {
            mContext.mOpponentIsReady = true;
        } // if

        if ((type & Constants.FLAG_GOAL) > 0) {
            mContext.goal(mIsServer);
            mContext.checkWin();
        }

        if ((type & Constants.FLAG_QUIT) > 0) {
            mIsRunning = false;
            mContext.mOpponentIsReady = false;
        }

        /* handle flags with attached data */
        if ((type & Constants.FLAG_INIT) > 0) {
            float size = mIn.readFloat();
            mContext.playerBlue.mSize = size;
            mContext.playerRed.mSize = size;
        } // if

        if ((type & Constants.FLAG_STATE) > 0) {
            mNetworkPlayer.mX = mIn.readFloat();
            float x = mIn.readFloat();
            float y = mIn.readFloat();
            float bt = mIn.readFloat();
            float dir = mIn.readFloat();
            float spe = mIn.readFloat();
            float spi = mIn.readFloat();
            if ((type & Constants.FLAG_BOUNCE) > 0 || (type & Constants.FLAG_SUPERBOUNCE) > 0) {
                mContext.ball.mX = x;
                mContext.ball.mY = y;
                mContext.ball.bounceTime = bt;
                mContext.ball.direction = dir;
                mContext.ball.speed = spe;
                mContext.ball.spin = spi;
                if ((type & Constants.FLAG_BOUNCE) > 0)
                    mContext.mSoundPool.play(mContext.BOUNCE_RAQUET_1, mContext.volume, mContext.volume, 0, 0, 1.0f);
                else
                    mContext.mSoundPool.play(mContext.BOUNCE_RAQUET_SMASH, mContext.volume, mContext.volume, 0, 0, 1.0f);
            } // if
        } // if
    } // receiveMessage

} // StreamThread
